import wx
import wx.richtext
import os
import threading
import asyncio
import re
import time
import requests
import random
import json


CHDIR = os.path.join(os.getcwd(), "mjson")

if not os.path.exists(CHDIR):
    os.mkdir(CHDIR)


class Error(object):
    def __init__(self, err, res, **kw):
        self.new_self(res, str(err.args))
        for key, value in kw.items():
            self.new_self(key, value)

    def new_self(self, obj, value):
        if not hasattr(self, obj):
            setattr(self, obj, value)



class MyDialog(wx.TextEntryDialog):
    def __init__(self, data, *args, **kw):
        wx.TextEntryDialog.__init__(self, *args, **kw)
        v_list = self.GetChildren()
        v_list[2].Bind(wx.EVT_BUTTON, lambda evt, obj=v_list[1], data=data : self.on_click(evt, obj, data))
        v_list[3].Bind(wx.EVT_BUTTON, lambda evt, obj=v_list[3], data=data : self.on_click(evt, obj, data))
        self.SetValue(str(time.time()).replace(".",""))
        self.Show()

    def on_click(self, eve, obj, data):
        if obj.GetLabel() == "取消":
            eve.Skip()
        else:
            if not os.path.exists(obj.GetValue() + ".json"):
                write_json_file(obj.GetValue() + ".json", data)
                eve.Skip()
            else:
                answer = wx.MessageBox("文件已存在!!!是否覆盖文件?", "提示", wx.YES_NO, self)
                if answer == wx.YES:
                    write_json_file(obj.GetValue() + ".json", data)
                    eve.Skip()


class Mrungui(wx.Frame):
    def __init__(self, parent, mythread):
        wx.Frame.__init__(self, parent, title = "任务列表", size = wx.Size(380, 240), style = wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX))
        mythread.connect(self.log)

        m_button = wx.Button(self, label="关闭")
        bSizer = wx.BoxSizer(wx.VERTICAL)
        self.log_txt = wx.ListCtrl(self, style=wx.LC_REPORT)
        self.log_txt.InsertColumn(0, "任务id")
        self.log_txt.InsertColumn(1, "状态")
        self.log_txt.SetColumnWidth(0, self.GetSize()[0]*0.75)
        self.log_txt.SetColumnWidth(1, self.GetSize()[0]*0.2)

        bSizer.Add(self.log_txt, 1, wx.EXPAND, 5)
        bSizer.Add(m_button, 0, wx.EXPAND, 5)

        self.SetAcceleratorTable(wx.AcceleratorTable([wx.AcceleratorEntry(wx.ACCEL_CTRL, ord('w'), m_button.GetId())]))
        self.SetSizer(bSizer)
        self.Layout()
        self.Centre(wx.BOTH)

        menu = wx.Menu()
        cancel = menu.Append(wx.NewIdRef(), "关闭任务", "")

        parent.Bind(wx.EVT_MOVE, lambda evt, obj=self.GetSize()[0]: self.on_move(evt, obj))
        m_button.Bind(wx.EVT_BUTTON, self.on_button)
        self.log_txt.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, lambda evt, obj=menu: self.open_menu(evt, obj))
        self.Bind(wx.EVT_MENU, lambda evt, obj=mythread: self.on_menu(evt, obj), cancel)

    def on_button(self, eve):
        self.Close()

    def open_menu(self, eve, obj):
        self.PopupMenu(obj)

    def on_menu(self, eve, obj):
        Id = self.log_txt.GetFirstSelected()
        if Id > -1:
            if self.log_txt.GetItem(Id, 1).GetText() == "False":
                obj._cancel(self.log_txt.GetItem(Id, 0).GetText())

    def on_move(self, eve, x):
        x = eve.GetPosition()[0] - x + 7
        self.Move((x, eve.GetPosition()[1] - 30))

    def log(self, data):
        self.log_txt.DeleteAllItems()
        for v in data:
            self.log_txt.Append((data[v]["id"],str(data[v]["run"])))


class Maingui(wx.Frame):
    def __init__(self, parent):
        wx.Frame.__init__(self, parent, title = "http单元测试", size = wx.Size(780, 500))
        self.SetSizeHints(wx.DefaultSize, wx.DefaultSize)
        bSizer = wx.BoxSizer(wx.VERTICAL)
        bSizer1 = wx.BoxSizer(wx.HORIZONTAL)
        bSizer1.Add(wx.StaticText(self, label="url"), 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5)
        url = wx.TextCtrl(self)
        bSizer1.Add(url, 1, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5)
        bSizer1.Add(wx.StaticText(self, label="method"), 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5)
        m_choice = wx.Choice(self, choices=["get", "post", "put", "patch"])
        m_choice.SetSelection(0)
        bSizer1.Add(m_choice, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5)
        m_button1 = wx.Button(self, label="保存配置")
        bSizer1.Add(m_button1, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5)
        bSizer.Add(bSizer1, 0, wx.EXPAND, 5)

        scroller_Sizer = wx.BoxSizer(wx.VERTICAL)
        self.scroller = wx.ScrolledWindow(self)
        self.scroller.SetScrollRate(0,20)

        mbSizer1 = wx.BoxSizer(wx.HORIZONTAL)
        self.init_gui(mbSizer1, "data")
        self.init_gui(mbSizer1, "json")
        scroller_Sizer.Add(mbSizer1, 0, wx.EXPAND, 5)

        mbSizer2 = wx.BoxSizer(wx.HORIZONTAL)
        self.init_gui(mbSizer2, "params")
        self.init_gui(mbSizer2, "auth")
        scroller_Sizer.Add(mbSizer2, 0, wx.EXPAND, 5)

        mbSizer3 = wx.BoxSizer(wx.HORIZONTAL)
        self.init_gui(mbSizer3, "headers")
        self.init_gui(mbSizer3, "cookies")
        scroller_Sizer.Add(mbSizer3, 0, wx.EXPAND, 5)
        
        bSizer.Add(self.scroller, 1, wx.EXPAND, 5)
        self.scroller.SetVirtualSize(self.GetSize())
        self.scroller.SetSizer(scroller_Sizer)

        bSizer5 = wx.BoxSizer(wx.VERTICAL)
        bSizer6 = wx.BoxSizer(wx.HORIZONTAL)
        self.richtextlog = wx.richtext.RichTextCtrl(self)
        self.richtextlog.SetDefaultStyle(wx.TextAttr(wx.Colour(180,90,0), wx.Colour(255,255,255)))
        bSizer6.Add(self.richtextlog, 1, wx.EXPAND, 5)
        self.richtextret = wx.richtext.RichTextCtrl(self)
        self.richtextret.SetDefaultStyle(wx.TextAttr(wx.Colour(255,0,0), wx.Colour(0,255,0)))
        bSizer6.Add(self.richtextret, 1, wx.EXPAND, 5)
        bSizer5.Add(bSizer6, 1, wx.EXPAND, 5)
        self.richtextlog.Clear()
        self.richtextret.Clear()

        bSizer7 = wx.BoxSizer(wx.HORIZONTAL)
        m_button2 = wx.Button(self, label="导入")
        bSizer7.Add(m_button2, 1, wx.ALL, 5 )
        m_button3 = wx.Button(self, label="请求")
        bSizer7.Add(m_button3, 1, wx.ALL, 5)
        bSizer5.Add(bSizer7, 0, wx.EXPAND, 5)
        bSizer.Add(bSizer5, 1, wx.EXPAND, 5)
        m_button0 = wx.Button(self, label="查看任务")
        m_button0.Hide()
        self.SetSizer(bSizer)
        self.Layout()
        self.Centre(wx.BOTH)
        myth = Mythreading(self)
        myth.start()
        ensic = [wx.AcceleratorEntry(wx.ACCEL_CTRL, ord('S'), m_button1.GetId()), wx.AcceleratorEntry(wx.ACCEL_NORMAL, 13, m_button3.GetId()), wx.AcceleratorEntry(wx.ACCEL_CTRL, ord('T'), m_button0.GetId())]
        self.SetAcceleratorTable(wx.AcceleratorTable(ensic))
        m_button0.Bind(wx.EVT_BUTTON, lambda evt, obj=Mrungui, myth=myth : self.on_create(evt, obj, myth))
        m_button1.Bind(wx.EVT_BUTTON, lambda evt, mark=1, arr=(mbSizer1, mbSizer2, mbSizer3), col=(url, m_choice) : self.on_click(evt, mark, arr, col))
        m_button2.Bind(wx.EVT_BUTTON, lambda evt, mark=2, arr=(mbSizer1, mbSizer2, mbSizer3), col=(url, m_choice) : self.on_click(evt, mark, arr, col))
        m_button3.Bind(wx.EVT_BUTTON, lambda evt, mark=3, arr=(mbSizer1, mbSizer2, mbSizer3), col=(url, m_choice, myth) : self.on_click(evt, mark, arr, col))

    def on_close(self, eve, obj, myth):
        if obj:
            self.Bind(wx.EVT_MOVE, None)
            ui = getattr(self, type(obj).__name__)
            myth.disconnect(ui.log)
            ui.Destroy()
            delattr(self, type(obj).__name__)

    def on_create(self, eve, obj, myth):
        if not hasattr(self, obj.__name__):
            setattr(self, obj.__name__, obj(self, myth))
            ui = getattr(self, obj.__name__)
            ui.Bind(wx.EVT_CLOSE, lambda evt, arr=ui, myth=myth : self.on_close(evt, arr, myth))
            ui.Show()
        else:
            ui = getattr(self, obj.__name__)
            self.on_close(eve, ui, myth)
            

    def on_click(self, eve, makr, arr_layout, col):
        if makr == 1:
            data = form_get_data(arr_layout)
            data.update({"url":col[0].GetValue(), "method":col[1].GetSelection()})
            dlg = MyDialog(data, self, '文件名称','保存文件')
            dlg.ShowModal()
            dlg.Destroy()
        elif makr == 2:
            dlg = wx.FileDialog(self, "json", CHDIR, "", "*.json", wx.FD_OPEN | wx.FD_FILE_MUST_EXIST)
            dlg.ShowModal()
            path = dlg.GetPath()
            dlg.Destroy()
            if not path:
                eve.Skip()
                return
            dic = open_json_file(path)
            col[0].SetValue(dic["url"])
            col[1].SetSelection(dic["method"])
            for k_layout in [out for lay in arr_layout for out in lay.GetChildren()]:
                p_sizer = k_layout.GetSizer().GetChildren()
                checkbox = p_sizer[0].GetWindow()
                if not dic[checkbox.GetLabel()]:
                    checkbox.SetValue(False)
                    self.send_event(wx.EVT_CHECKBOX.typeId, eve.GetId(), checkbox)
                    continue
                len_dic = len(dic[checkbox.GetLabel()])
                x = len(p_sizer) - 2 - len_dic
                abs_x = abs(x)
                if len(p_sizer) == 1:
                    button1, button2 = self.add_controls(k_layout.GetSizer(), checkbox.GetLabel())
                    checkbox.SetValue(True)
                else:    
                    button1 = p_sizer[1].GetSizer().GetChildren()[0].GetWindow()
                    button2 = p_sizer[1].GetSizer().GetChildren()[1].GetWindow()
                for v in range(abs_x + len(p_sizer)):
                    if x > 0 and v < abs_x:
                        self.send_event(wx.EVT_BUTTON.typeId, eve.GetId(), button2)
                    elif x < 0 and v + 1 > len_dic:
                        break
                    elif v < abs_x:
                        self.send_event(wx.EVT_BUTTON.typeId, eve.GetId(), button1, dic[checkbox.GetLabel()][len(p_sizer) - 2 + v])
                    elif v > abs_x and v - abs_x < len_dic + 1:
                        t = dic[checkbox.GetLabel()][v - abs_x - 1]
                        p_sizer[v - abs_x + 1].GetSizer().GetChildren()[0].GetWindow().SetValue(tuple(t)[0])
                        p_sizer[v - abs_x + 1].GetSizer().GetChildren()[1].GetWindow().SetValue(t[tuple(t)[0]])
        elif makr == 3:
            url = re.sub("[\n,\r,\t, ]", "", col[0].GetValue())
            if url and url_check(url):
                method = col[1].GetString(col[1].GetSelection())
                data = form_get_data(arr_layout)
                Id = str(time.time()).replace(".","")[0:15]
                self.add_log(time.strftime("%Y-%m-%d %H:%M:%S") + " 创建任务: " + Id, self.richtextlog)
                col[2].add_coro(call_requests, Id, url, method, data)
            else:
                wx.MessageBox("链接格式有误", "提示", wx.OK_DEFAULT, self)
        eve.Skip()

    def on_button(self, eve, bsizer, n):
        if n == 1:
            data = eve.GetClientData()
            k_text = wx.TextCtrl(self.scroller)
            v_text = wx.TextCtrl(self.scroller)
            if data:
                k_text.SetValue(tuple(data)[0])
                v_text.SetValue(data[tuple(data)[0]])
            Sizer = wx.BoxSizer(wx.HORIZONTAL)
            Sizer.Add(k_text, 0, wx.ALL, 5)
            Sizer.Add(v_text, 0, wx.ALL, 5)
            bsizer.Add(Sizer, 0, wx.ALIGN_CENTER_HORIZONTAL, 5)
        else:
            b = bsizer.GetChildren()
            if len(b) > 2:
                d = b[-1].GetSizer()
                for x in d.GetChildren():
                    x.GetWindow().Destroy()
                bsizer.Remove(d)
        self.Layout()
        eve.Skip()

    def on_checkbox(self, eve, bsizer, lable):
        if eve.IsChecked():
            self.add_controls(bsizer, lable)
        else:
            for k, v in enumerate(bsizer.GetChildren()):
                if v.IsSizer():
                    for x in v.GetSizer().GetChildren():
                        if k == 1:
                            x.GetWindow().Bind(wx.EVT_BUTTON, None)
                        x.GetWindow().Destroy()
                    bsizer.Remove(v.GetSizer())
        self.Layout()
        eve.Skip()

    def send_event(self, Typeid, Id, Parent, data=None):
        Newevent = wx.CommandEvent(Typeid, Id)
        Newevent.SetClientData(data)
        Newevent.SetEventObject(Parent)
        wx.PostEvent(Parent, Newevent)

    def init_gui(self, all_Sizer, lable):
        bsizer = wx.BoxSizer(wx.VERTICAL)
        checkBox = wx.CheckBox(self.scroller, label=lable)
        checkBox.SetValue(True)
        bsizer.Add(checkBox, 0, wx.ALL|wx.ALIGN_CENTER_HORIZONTAL, 5)
        self.add_controls(bsizer, lable)
        all_Sizer.Add(bsizer, 1, 0, 5)
        checkBox.Bind(wx.EVT_CHECKBOX, lambda evt, bsizer=bsizer, lable=lable : self.on_checkbox(evt, bsizer, lable))
        return checkBox

    def add_controls(self, bsizer, lable):
        sn_bSizer = wx.BoxSizer(wx.HORIZONTAL)
        addbutton = wx.Button(self.scroller, label="添加 " + lable + " 键值")
        delbutton = wx.Button(self.scroller, label="删除 " + lable + " 键值")
        sn_bSizer.Add(addbutton, 0, wx.ALL, 5)
        sn_bSizer.Add(delbutton, 0, wx.ALL, 5)
        bsizer.Add(sn_bSizer, 0, wx.ALIGN_CENTER_HORIZONTAL, 5)
        addbutton.Bind(wx.EVT_BUTTON, lambda evt, mark=bsizer, on=1 : self.on_button(evt, mark, on))
        delbutton.Bind(wx.EVT_BUTTON, lambda evt, mark=bsizer, on=2 : self.on_button(evt, mark, on))
        return addbutton, delbutton

    def add_log(self, log, obj):
        obj.AddParagraph(log)
        obj.AppendText(" ")
        obj.ShowPosition(-1)



class Mythreading(threading.Thread):
    def __init__(self,windows):
        threading.Thread.__init__(self)
        self.windows = windows
        self.daemon  = True
        self.loop    = asyncio.new_event_loop()
        self.runapp  = {}
        self.call_list = []

    def run(self):
        self.loop.run_forever()

    def add_coro(self, call, Id, *agrs):
        res = asyncio.run_coroutine_threadsafe(call(self, Id, *agrs), self.loop)
        self.runapp.update({Id: {"app":res, "run":False, "id":Id}})
        self.emit(self.runapp)

    def connect(self, call):
        self.call_list.append(call)

    def disconnect(self, call):
        self.call_list.remove(call)

    def emit(self, *argv):
        for call in self.call_list:
            wx.CallAfter(call,*argv)

    def _cancel(self, Id):
        if Id in self.runapp:
            if not self.runapp[Id]["run"]:
                #self.runapp[Id]["app"].cancel()
                self.runapp.pop(Id)
                self.emit(self.runapp)


async def call_requests(self, Id, *arr_data):
    if Id not in self.runapp:
        return ""    
    self.runapp[Id].update({"run":True})
    self.emit(self.runapp)
    url     = arr_data[0]
    method  = arr_data[1]
    key_dic = arr_data[2]
    key_dic = {v : {key : item[key] for item in key_dic[v] for key in item} for v in key_dic if key_dic[v]}
    if method == "get":
        key_dic.setdefault('allow_redirects', True)
    try:
        with requests.Session() as session:
            ret = session.request(method, url, **key_dic)
    except Exception as e:
        ret = Error(e, "text", status_code=800)
    self.runapp.pop(Id)
    self.emit(self.runapp)
    wx.CallAfter(self.windows.add_log, time.strftime("%Y-%m-%d %H:%M:%S") + " 任务结束: " + Id, self.windows.richtextlog)
    wx.CallAfter(self.windows.add_log, str(ret.status_code) + " [ " + ret.text + " ]", self.windows.richtextret)


def form_get_data(arr_layout):
    dic = {}
    for k_layout in [out for lay in arr_layout for out in lay.GetChildren()]:
        p_sizer = k_layout.GetSizer().GetChildren()
        k = p_sizer[0].GetWindow().GetLabel()
        dic.update({k:[]})
        for i in range(1, len(p_sizer)):
            sizer = p_sizer[i].GetSizer().GetChildren()
            if hasattr(sizer[0].GetWindow(),"GetValue"):
                key = sizer[0].GetWindow().GetValue()
                if key:
                    dic[k].append({key:sizer[1].GetWindow().GetValue()})
    return dic


def url_check(url):
    if re.match("^http[s]?://.*", url):
        return True
    return False


def open_json_file(file):
    with open(os.path.join(CHDIR,file), "r", encoding="utf-8") as f:
        data = f.read()
    return json.loads(data)


def write_json_file(file, data):
    with open(os.path.join(CHDIR,file), "w", encoding="utf-8") as f:
        f.write(json.dumps(data))
    return True




if __name__ == '__main__':
    app = wx.App()
    Maingui(None).Show(True)
    app.MainLoop()